﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace Social.Logic.Comment
{
    /// <summary>
    /// 加密解密相关。
    /// </summary>
    public class Cryptography
    {
        /// <summary>
        /// 初始化Cryptography的新实例。
        /// </summary>
        /// <param name="salt">密盐。</param>
        /// <param name="defaultEncoding">默认的字符串编码方式。</param>
        public Cryptography(string salt, Encoding defaultEncoding)
        {
            if (string.IsNullOrWhiteSpace(salt))
            {
                throw new ArgumentNullException("salt");
            }
            if (defaultEncoding == null)
            {
                throw new ArgumentNullException("defaultEncoding");
            }
            this.Salt = salt;
            this.DefaultEncoding = defaultEncoding;
        }

        /// <summary>
        /// 密盐。
        /// </summary>
        protected string Salt
        {
            get;
            set;
        }

        /// <summary>
        /// 默认字符串编码方式。
        /// </summary>
        public Encoding DefaultEncoding
        {
            get;
            protected set;
        }

        /// <summary>
        /// 获取非对称算法提供程序。
        /// </summary>
        /// <returns>非对称算法提供程序</returns>
        protected HashAlgorithm GetHashProvider()
        {
            return new MD5CryptoServiceProvider();
        }

        /// <summary>
        /// 获取对称算法提供程序。
        /// </summary>
        /// <returns>对称算法提供程序。</returns>
        protected SymmetricAlgorithm GetSymmetricProvider()
        {
            return new AesCryptoServiceProvider() { Padding = PaddingMode.PKCS7, Mode = CipherMode.CBC };
        }

        /// <summary>
        /// 获取对称加密密钥。
        /// </summary>
        /// <param name="key">密钥字符串。</param>
        /// <returns>密钥字节数组。</returns>
        public virtual byte[] GetSymmetricKey(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key");
            }
            byte[] hash = HashCore(DefaultEncoding.GetBytes(key + Salt));
            byte[] result = new byte[32];
            for (int i = 0; i < 2; i++)
            {
                Buffer.BlockCopy(hash, 0, result, i * 16, 16);
            }
            return result;
        }

        /// <summary>
        /// 获取对称加密偏移向量。
        /// </summary>
        /// <param name="vector">偏移向量字符串。</param>
        /// <returns>偏移向量字节数组。</returns>
        public virtual byte[] GetSymmetricVector(string vector)
        {
            if (string.IsNullOrWhiteSpace(vector))
            {
                throw new ArgumentNullException("vector");
            }
            byte[] hash = HashCore(DefaultEncoding.GetBytes(vector + Salt));
            byte[] result = new byte[16];
            for (int i = 0; i < 1; i++)
            {
                Buffer.BlockCopy(hash, 0, result, i * 16, 16);
            }
            return result;
        }

        /// <summary>
        /// 将一个字节数组进行Hash转换。
        /// </summary>
        /// <param name="input">需要转换的字节数组。</param>
        /// <returns>Hash转换后的字节数组。</returns>
        public virtual byte[] HashCore(byte[] input)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }
            return GetHashProvider().ComputeHash(input);
        }

        /// <summary>
        /// 将一个字节数组进行Hash转换。
        /// </summary>
        /// <param name="input">需要转换的字节数组。</param>
        /// <returns>Hash转换后的字符串（将转换后的字节数组值转换为它的等效十六进制字符串表示形式，不包含‘-’间隔）。</returns>
        public virtual string Hash(byte[] input)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }
            return BitConverter.ToString(HashCore(input)).Replace("-", "");
        }

        /// <summary>
        /// 将原始字符串进行Hash转换。
        /// </summary>
        /// <param name="password">原始字符串。</param>
        /// <param name="encoding">字符串转换为字节数组的编码方式。</param>
        /// <returns>Hash后的字符串。</returns>
        public virtual string Hash(string input, Encoding encoding)
        {
            if (string.IsNullOrWhiteSpace(input))
            {
                throw new ArgumentNullException("input");
            }
            return Hash(encoding.GetBytes(input)).Substring(0, 20);
        }

        /// <summary>
        /// 将原始字符串进行Hash转换。
        /// </summary>
        /// <param name="password">原始字符串。</param>
        /// <returns>Hash后的字符串。</returns>
        public virtual string Hash(string input)
        {
            return Hash(input, DefaultEncoding);
        }

        /// <summary>
        /// 将原始字符Hash为长度为20的字符串。
        /// </summary>
        /// <param name="password">原始字符串。</param>
        /// <returns>Hash后的字符串。</returns>
        public virtual string CreatePassword(string password)
        {
            if (string.IsNullOrWhiteSpace(password))
            {
                throw new ArgumentNullException("input");
            }
            for (int i = 0; i < 3; i++)
            {
                password = Hash(password.Trim().ToUpper() + Salt).Substring(0, 20);
            }
            return password;
        }

        /// <summary>
        /// 验证原始字符串Hash后的结果与指定的Hash字符串是否一致（不区分大小写）。
        /// </summary>
        /// <param name="password">原始字符串。</param>
        /// <param name="hashPassword">Hash字符串。</param>
        /// <returns>如果一致则返回ture，否则返回false。</returns>
        public bool VerifyPassowrd(string password, string hashPassword)
        {
            if (string.IsNullOrWhiteSpace(password))
            {
                throw new ArgumentNullException("password");
            }
            if (string.IsNullOrWhiteSpace(hashPassword))
            {
                throw new ArgumentNullException("hashPassword");
            }
            return CreatePassword(password).Equals(hashPassword);
        }

        /// <summary>
        /// 将字节数组进行对称加密。
        /// </summary>
        /// <param name="input">需要加密的字节数组。</param>
        /// <param name="key">密钥。</param>
        /// <param name="vector">偏移向量。</param>
        /// <returns>加密后的字节数组。</returns>
        public byte[] SymmetricEncrypt(byte[] input, string key, string vector)
        {
            if (input == null || input.Length == 0)
            {
                throw new ArgumentException("input");
            }
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key");
            }
            if (string.IsNullOrWhiteSpace(vector))
            {
                throw new ArgumentNullException("vector");
            }
            using (MemoryStream memory = new MemoryStream())
            {
                using (CryptoStream crypto = new CryptoStream(memory,
                    GetSymmetricProvider().CreateEncryptor(GetSymmetricKey(key), GetSymmetricVector(vector)),
                    CryptoStreamMode.Write))
                {
                    crypto.Write(input, 0, input.Length);
                    crypto.FlushFinalBlock();
                    return memory.ToArray();
                }
            }
        }

        /// <summary>
        /// 将字节数组进行对称解密。
        /// </summary>
        /// <param name="input">需要解密的字节数组。</param>
        /// <param name="key">密钥。</param>
        /// <param name="vector">偏移向量。</param>
        /// <returns>解密后的字节数组。</returns>
        public byte[] SymmetricDecrypt(byte[] input, string key, string vector)
        {
            if (input == null || input.Length == 0)
            {
                throw new ArgumentException("input");
            }
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key");
            }
            if (string.IsNullOrWhiteSpace(vector))
            {
                throw new ArgumentNullException("vector");
            }
            using (MemoryStream memory = new MemoryStream())
            {
                using (CryptoStream crypto = new CryptoStream(memory,
                    GetSymmetricProvider().CreateDecryptor(GetSymmetricKey(key), GetSymmetricVector(vector)),
                    CryptoStreamMode.Write))
                {
                    crypto.Write(input, 0, input.Length);
                    crypto.FlushFinalBlock();
                    return memory.ToArray();
                }
            }
        }

        /// <summary>
        /// 将指定的字符串进行对称加密并将加密后的字节数组转换为Base64格式的字符串。
        /// </summary>
        /// <param name="input">原始字符串。</param>
        /// <returns>加密后的Base64字符串。</returns>
        public string SymmetricEncrypt(string input, string key, string vector)
        {
            if (string.IsNullOrWhiteSpace(input))
            {
                throw new ArgumentNullException("input");
            }
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key");
            }
            if (string.IsNullOrWhiteSpace(vector))
            {
                throw new ArgumentNullException("vector");
            }
            return Convert.ToBase64String(SymmetricEncrypt(DefaultEncoding.GetBytes(input), key, vector));
        }

        /// <summary>
        /// 将指定的Base64字符串进行对称解密并将解密后的字节数组按照默认编码方式转换为原始字符串。
        /// </summary>
        /// <param name="input">加密后的Base64字符串。</param>
        /// <returns>解密后的字符串。</returns>
        public string SymmetricDecrypt(string input, string key, string vector)
        {
            if (string.IsNullOrWhiteSpace(input))
            {
                throw new ArgumentNullException("input");
            }
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key");
            }
            if (string.IsNullOrWhiteSpace(vector))
            {
                throw new ArgumentNullException("vector");
            }
            return DefaultEncoding.GetString(SymmetricDecrypt(Convert.FromBase64String(input), key, vector));
        }
    }
}
